/*
 * Cabinet.java
 *
 *  
 */
package org.msb.finance.data;

import java.math.BigDecimal;
import java.util.*;

/**
 * The {@code Cabinet} class is the top-level container object of all the financial data stored in a single file.
 * 
 * @author Marc Boudreau
 */
public class Cabinet {

	/*
	 * 
	 */
	private Map<String, Account> accounts;

	/*
	 * 
	 */
	private Map<String, Payee> payees;

	/*
	 * 
	 */
	private Set<Individual> individuals;

	/*
	 * The rootCategory object is a special kind of Category that has no name and is practically invisible to the
	 * application, its only purpose to act as a container for the top-level Categories.
	 */
	private RootCategory rootCategory;

	/*
	 * 
	 */
	private Set<RecurringTransaction> recurringTransactions;

	/*
	 * 
	 */
	private Budget budget;

	/*
	 * 
	 */
	private Date created;

	/*
	 * 
	 */
	private String password;

	/*
	 * 
	 */
	private SortedSet<Currency> currencies;

	/**
	 * Creates a new {@code Cabinet} object. The new instance is initialized with the default set of {@link Category}
	 * objects.
	 * 
	 * @param hashedPassword
	 */
	public Cabinet(String hashedPassword) {
		accounts = new HashMap<String, Account>();
		payees = new HashMap<String, Payee>();
		individuals = new HashSet<Individual>();
		rootCategory = new RootCategory();

		this.recurringTransactions = new HashSet<RecurringTransaction>();
		this.currencies = new TreeSet<Currency>(new CurrencyComparator());

		this.currencies.add(Currency.getInstance("USD"));
		this.currencies.add(Currency.getInstance("CAD"));

		this.created = new Date();
		this.password = hashedPassword;
	}

	/**
	 * 
	 * @param account
	 * 
	 * @return
	 */
	public boolean addAccount(Account account) {
		if (null == account) {
			return false;
		}

		synchronized (accounts) {
			if (accounts.containsKey(account.getName())) {
				return false;
			}

			accounts.put(account.getName(), account);
		}

		return true;
	}

	/**
	 * 
	 * @param account
	 * 
	 * @return
	 */
	public boolean removeAccount(Account account) {
		if (null == account) {
			return false;
		}

		synchronized (accounts) {
			return null != accounts.remove(account.getName());
		}
	}

	/**
	 * 
	 * @param account
	 * @param newName
	 * 
	 * @return
	 */
	public boolean renameAccount(Account account, String newName) {
		if (null == account || null == newName) {
			return false;
		}

		if (newName.equals(account.getName())) {
			return true;
		}

		synchronized (accounts) {
			if (accounts.containsKey(newName)) {
				return false;
			}

			accounts.remove(account.getName());
			accounts.put(newName, account);
			account.setName(newName);
		}

		return true;
	}

	/**
	 * 
	 * @return
	 */
	public List<Account> getAccounts() {
		return new ArrayList<Account>(accounts.values());
	}

	/**
	 * 
	 * @return
	 */
	public BigDecimal getBalance() {
		BigDecimal balance = new BigDecimal(0);

		for (Account account : accounts.values()) {
			balance.add(account.getBalance());
		}

		return balance;
	}

	/**
	 * 
	 * @param individual
	 */
	public void addIndividual(Individual individual) {
		this.individuals.add(individual);
	}

	/**
	 * 
	 * @param individual
	 */
	public void removeIndividual(Individual individual) {
		this.individuals.remove(individual);
	}

	/**
	 * 
	 * @return
	 */
	public Set<Individual> getIndividuals() {
		return new HashSet<Individual>(this.individuals);
	}

	/**
	 * 
	 * @param payee
	 * @return
	 */
	public boolean addPayee(Payee payee) {
		if (null == payee) {
			return false;
		}

		synchronized (payees) {
			if (payees.containsKey(payee.getName())) {
				return false;
			}

			payees.put(payee.getName(), payee);
		}

		return true;
	}

	/**
	 * 
	 * @param payee
	 * @return
	 */
	public boolean removePayee(Payee payee) {
		if (null == payee) {
			return false;
		}

		synchronized (payees) {
			return null != payees.remove(payee.getName());
		}
	}

	/**
	 * 
	 * @param payee
	 * @param newName
	 * @return
	 */
	public boolean renamePayee(Payee payee, String newName) {
		if (null == payee || null == newName) {
			return false;
		}

		if (newName.equals(payee.getName())) {
			return true;
		}

		synchronized (payees) {
			if (payees.containsKey(newName)) {
				return false;
			}

			payees.remove(payee.getName());
			payees.put(newName, payee);
			payee.setName(newName);
		}

		return true;
	}

	/**
	 * 
	 * @param category
	 * @param parentCategory
	 * @return
	 */
	public boolean addCategory(Category category, Category parentCategory) {
		if (null == category) {
			return false;
		}

		if (null == parentCategory) {
			parentCategory = rootCategory;
		}

		return parentCategory.addSubCategory(category);
	}

	/**
	 * 
	 * @param category
	 * @return
	 */
	public boolean deleteCategory(Category category) {
		if (null == category) {
			return false;
		}

		return category.removeFromParent();
	}

	/**
	 * 
	 * @param category
	 * @param newName
	 * @return
	 */
	public boolean renameCategory(Category category, String newName) {
		if (null == category || null == newName) {
			return false;
		}

		return category.rename(newName);
	}

	/**
	 * 
	 * @param category
	 * @param destinationParentCategory
	 * @return
	 */
	public boolean moveCategory(Category category, Category destinationParentCategory) {
		if (null == category || null == destinationParentCategory) {
			return false;
		}

		category.removeFromParent();
		return destinationParentCategory.addSubCategory(category);
	}

	/**
	 * 
	 * @return
	 */
	public Iterator<Category> getCategories() {
		return rootCategory.getSubCategories();
	}

	/**
	 * 
	 * @return x
	 */
	public Date getCreationTime() {
		return (Date) this.created.clone();
	}

	/**
	 * 
	 * @return s
	 */
	public Iterator<Currency> getCurrencies() {
		return this.currencies.iterator();
	}

	/**
	 * 
	 * 
	 * 
	 * @author Marc Boudreau
	 * 
	 */
	public class CurrencyComparator implements Comparator<Currency> {

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
		 */
		public int compare(Currency o1, Currency o2) {
			return o1.getCurrencyCode().compareTo(o2.getCurrencyCode());
		}

	}

	private class RootCategory extends Category {

		/**
		 * @param name
		 */
		public RootCategory() {
			super(null);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.msb.finance.data.Category#getFullName()
		 */
		@Override
		public String getFullName() {
			return null;
		}
	}
}
